/* @flow */
/**
 * 只导出一个函数：handleError：用来实现 Vue.config.errorHandler 这一配置功能
 * 
 */
import config from '../config'
import { warn } from './debug'
import { inBrowser, inWeex } from './env'

// 用于错误处理 err：捕获的错误对象， vm: Vue实例， info: 特定的错误提示信息
export function handleError (err: Error, vm: any, info: string) {
  if (vm) {
    let cur = vm
    // 逐层寻找父级组件，如果父级组件：这种处理方式会使得一个子组件报错，其所有使用了errorCaptured的父组件都可以捕获到这个错误
    while ((cur = cur.$parent)) {
      // errorCaptured 选项可以用来捕获子代组件的错误，当子组件有错误被 handleError 函数处理时，父组件可以通过该选项捕获错误
      const hooks = cur.$options.errorCaptured
      // errorCaptured 选项在内部是以一个数组的形式存在的
      if (hooks) {
        for (let i = 0; i < hooks.length; i++) {
          try {
            const capture = hooks[i].call(cur, err, vm, info) === false
            // 如果 errorCaptured 钩子函数返回假，那么 capture 为真直接 return
            // 此处直接返回可阻止错误继续 向上传递。
            if (capture) return
          } catch (e) {
            globalHandleError(e, cur, 'errorCaptured hook')
          }
        }
      }
    }
  }
  globalHandleError(err, vm, info)
}

function globalHandleError (err, vm, info) {
  // 先判断 config.errorHandler 是否为真(config.errorHandler: 是Vue 全局API提供的用于自定义错误处理的配置)
  if (config.errorHandler) {
    try {
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      logError(e, null, 'config.errorHandler')
    }
  }
  // 为假时采用默认的处理方式
  logError(err, vm, info)
}

// 打印错误信息
function logError (err, vm, info) {
  // 在非生产环境下，先给出一个警告
  if (process.env.NODE_ENV !== 'production') {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }
  // 然后判断是否在浏览器或者Weex环境且 console 是否可用
  if ((inBrowser || inWeex) && typeof console !== 'undefined') {
    console.error(err)
  } else {
    throw err
  }
}
